package com.wb.spring.boot.autoconfigure.proxy;

import com.wb.spring.boot.autoconfigure.stereotype.WbClient;
import com.wb.spring.boot.autoconfigure.stereotype.WbRequestBody;
import com.wb.spring.boot.autoconfigure.stereotype.WbRequestMapping;
import com.wb.spring.boot.autoconfigure.stereotype.WbRequestParam;
import javassist.*;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.annotation.Annotation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 调用代理对象
 * @author wenbin.xu
 * @date 2022/9/2 17:26
 */
public class WbClientProxy {

    /**
     * 生成的代理对象名称前缀
     */
    private static final String PROXY_PREFIX = "WbRpcProxy";

    /**
     * 生成的代理对象名称后缀
     */
    private static final String PROXY_SUFFIX = "Impl";

    /**
     * 参数为@WbRequestBody的类型
     */
    private static final String REQUEST_BODY_PARAMETER = "REQUEST_BODY_PARAMETER";

    public static <T> Class<T> buildProxy(Class<T> t) throws NotFoundException, CannotCompileException, ClassNotFoundException {
        // 获取调用的服务名称
        String clientServerName = t.getAnnotation(WbClient.class).value();
        ClassPool pool = ClassPool.getDefault();
        //创建代理类对象
        CtClass ctClass = pool.makeClass(getImplName(t));
        //设置代理类的接口
        CtClass interj = pool.getCtClass(t.getName());
        // 设置方法的属性
        initClassField(ctClass, interj, pool);
        //代理类的所有方法
        CtMethod[] methods = interj.getDeclaredMethods();
        // 获取所有方法的参数信息
        Map<String, List<String>> parameterMap = buildParametersMap(t);

        for(CtMethod method : methods) {
            // 创建代理类的方法
            CtMethod ctMethod = initClassMethod(method, ctClass, parameterMap, t, clientServerName);
            ctClass.addMethod(ctMethod);
        }
        return (Class) ctClass.toClass();
    }

    private static CtMethod initClassMethod(CtMethod method, CtClass ctClass, Map<String,
            List<String>> parameterMap, Class<?> t, String clientServerName) throws NotFoundException, ClassNotFoundException, CannotCompileException {
        String methodName = method.getName();
        CtMethod cm = new CtMethod(method.getReturnType(), methodName, method.getParameterTypes(), ctClass);
        // 获取参数列表名称
        String methodFullName = buildMethodFullName(method);
        // 该方法参数的映射值
        List<String> parameterNames = parameterMap.get(methodFullName);
        // 解析访问的路径
        WbRequestMapping requestMapping = (WbRequestMapping) method.getAnnotation(WbRequestMapping.class);
        StringBuilder url = new StringBuilder(requestMapping.value());
        boolean containSymbol = false;
        String requestBody = "null";
        if (!CollectionUtils.isEmpty(parameterNames)) {
            String connect = "?";
            for (int i = 0; i < parameterNames.size(); i++) {
                String parameterName = parameterNames.get(i);
                if (StringUtils.EMPTY.equals(parameterName)) {
                    System.err.println(t.getName() + "类中，" + method.getName() + "方法的第" + (i + 1) + "参数没有加WbRequestParam注解，无法匹配，请添加WbRequestParam，或在打包时，使用-parameters，可根据参数名自动匹配。");
                    continue;
                }
                if (REQUEST_BODY_PARAMETER.equals(parameterName)) {
                    requestBody = "$" + (i + 1);
                    continue;
                }
                containSymbol = true;
                if ("?".equals(connect)) {
                    url = new StringBuilder(url + connect + parameterName + "=\"+" + "$" + (i + 1));
                } else {
                    url.append("+\"").append(connect).append(parameterName).append("=\"+").append("$").append(i + 1);
                }
                connect = "&";
            }
        }

        if (!containSymbol) {
            url = new StringBuilder(url + "\"");
        }
        String returnType = "void".equals(cm.getReturnType().getName()) ? "com.wb.spring.boot.autoconfigure.proxy.Void" : cm.getReturnType().getName();
        String isReturn = "com.wb.spring.boot.autoconfigure.proxy.Void".equals(returnType) ? "" : "return (" + returnType + ")";
        cm.setBody("{" + isReturn + " clientCall.call(\"http://\" + register.getServer(\"" + clientServerName + "\") + \"" + url + ",\"" + requestMapping.method() + "\", " + requestBody + ",  Class.forName(\"" + returnType+ "\"));}");

        return cm;
    }

    /**
     * 创建类的属性
     * @param ctClass
     *  生成的代理类
     * @param interj
     *  代理类的接口
     * @param pool
     * @return
     * @throws CannotCompileException
     * @throws NotFoundException
     */
    private static CtClass initClassField(CtClass ctClass, CtClass interj, ClassPool pool) throws CannotCompileException, NotFoundException {
        CtClass[] interfaces = new CtClass[]{interj};
        ctClass.setInterfaces(interfaces);
        // 获取常量池
        ConstPool constPool = ctClass.getClassFile().getConstPool();
        // 创建属性，调用远程服务使用
        CtField ctField  = new CtField(pool.get("com.wb.spring.boot.autoconfigure.proxy.WbClientCall"), "clientCall", ctClass);
        ctField.setModifiers(AccessFlag.PUBLIC);
        FieldInfo fieldInfo = ctField.getFieldInfo();
        // 添加Autowired注解，后续注册到spring中会自动装配
        AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        Annotation filedAnnotation = new Annotation(Autowired.class.getName(), constPool);
        annotationsAttribute.addAnnotation(filedAnnotation);
        fieldInfo.addAttribute(annotationsAttribute);

        CtField registerField  = new CtField(pool.get("com.wb.spring.boot.autoconfigure.register.IRegister"), "register", ctClass);
        ctField.setModifiers(AccessFlag.PUBLIC);
        FieldInfo registerFieldInfo = registerField.getFieldInfo();
        registerFieldInfo.addAttribute(annotationsAttribute);
        // 加入到类文件中
        ctClass.addField(ctField);
        ctClass.addField(registerField);
        return ctClass;
    }

    /**
     * 获取全限定名称
     * @param method
     * @return
     * @throws NotFoundException
     */
    private static String buildMethodFullName(CtMethod method) throws NotFoundException {
        StringBuilder methodFullName = new StringBuilder(method.getName());
        if (method.getParameterTypes() != null) {
            for (CtClass parameterType : method.getParameterTypes()) {
                methodFullName.append(parameterType.getName());
            }
        }

        return methodFullName.toString();
    }

    /**
     * key-方法名称+方法参数类型
     * value-方法参数名称集合
     * @param clazz
     *  要解析的类
     *
     * @return
     *  方法的类对应参数名称
     */
    private static Map<String, List<String>> buildParametersMap(Class<?> clazz) {
        // 同名参数映射使用 此处使用1.8之上的--parameters才能支持，否则只能使用注解映射。
        DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
        Map<String, List<String>> parameterMap = new HashMap<>(8);
        for (Method method : clazz.getMethods()) {
            StringBuilder name = new StringBuilder(method.getName());
            List<String> parameterRequestNames = new ArrayList<>();
            if (method.getParameters() != null) {
                for (Parameter parameter : method.getParameters()) {
                    name.append(parameter.getType().getName());
                    WbRequestParam annotation = parameter.getAnnotation(WbRequestParam.class);
                    String parameterRequestName = StringUtils.EMPTY;
                    if (annotation != null) {
                        parameterRequestName = annotation.value();
                    }
                    WbRequestBody wbRequestBody = parameter.getAnnotation(WbRequestBody.class);
                    if (wbRequestBody != null) {
                        parameterRequestName = REQUEST_BODY_PARAMETER;
                    }
                    parameterRequestNames.add(parameterRequestName);
                }
            }

            String[] parameterNames = discoverer.getParameterNames(method);
            if (parameterNames == null) {
                parameterMap.put(name.toString(), parameterRequestNames);
                continue;
            }
            List<String> correctParameterRequestNames = new ArrayList<>();
            for (int i = 0; i < parameterRequestNames.size(); i++) {
                String parameterRequestName = parameterRequestNames.get(i);
                if (StringUtils.EMPTY.equals(parameterRequestName)) {
                    correctParameterRequestNames.add(parameterNames[i]);
                    continue;
                }
                correctParameterRequestNames.add(parameterRequestName);
            }

            parameterMap.put(name.toString(), correctParameterRequestNames);
        }

        return parameterMap;
    }
    /**
     * 获取代理类的名称
     * @param t
     *  实现类类型
     * @return
     *  实现类名称
     */
    private static <T> String getImplName(Class<T> t) {
        return t.getPackage() + "." + PROXY_PREFIX  + t.getSimpleName() + PROXY_SUFFIX;
    }
}
